use crate::common::*;

use std::collections::{HashMap, HashSet};

use pyo3::prelude::*;
use pyo3::types::{PyAny, PyBytes, PyString};
use serde::Deserialize;

#[derive(Deserialize)]
struct HuobiRecordData {
    ch: String,
    tick: AsksBids,
}

#[pyclass]
pub struct HuobiHandler {
    books: HashMap<String, Py<BookBuilder>>,
    book_callbacks: Vec<PyObject>,
    book_symbols: HashSet<String>,
    py_fromtimestamp: PyObject,
    py_get_product: PyObject,
}

#[pymethods]
impl HuobiHandler {
    #[new]
    fn new_python(obj: &PyRawObject, py: Python) {
        let module = py
            .import("coin.exchange.huobi.kr_rest.product")
            .unwrap();
        let cls: PyObject = module.get("HuobiProduct").unwrap().to_object(py);

        obj.init(HuobiHandler {
            books: HashMap::new(),
            book_symbols: HashSet::new(),
            book_callbacks: Vec::new(),
            py_fromtimestamp: py
                .import("datetime")
                .unwrap()
                .get("datetime")
                .unwrap()
                .to_object(py)
                .getattr(py, "fromtimestamp")
                .unwrap(),
            py_get_product: cls.getattr(py, "FromStrNativeProduct").unwrap(),
        });
    }

    fn on_snapshot(
        &mut self,
        py: Python,
        timestamp: i64,
        record_bytes: &PyBytes,
        _queue_data: &PyAny,
        _topic_data: &PyAny,
    ) -> PyResult<()> {
        // record: TopicRecord(queue_seq=1533, topic_id=260648680952608, topic_seq=0, timestamp=1567296016429961000, data=<memory at 0x7fdf32316b88>)
        let bytes: &[u8] = record_bytes.as_bytes();

        let record = Record::<HuobiRecordData> {
            timestamp: timestamp,
            // data: serde_json::from_slice(&bytes[..]).expect("cannot parse json"),
            data: serde_json::from_slice(bytes).expect("cannot parse json"),
        };

        // "channel" string:
        // market.BTC_CQ.depth.step0
        // market.XRP_CQ.depth.step0
        // market.EOS_CQ.depth.step0
        // ..
        // "market.{base_str}_{native_subscription_contract}.{event_type}.{arg}"
        //
        // market.neousdt.depth.step0
        // market.{symbol}.{event_type}.{arg}

        let native_symbol: String = {
            let channel = &record.data.ch;
            let pos1 = channel.find('.').unwrap();
            let pos2 = pos1 + 1 + (&channel[pos1 + 1..]).find('.').unwrap();
            let symbol = &channel[pos1 + 1..pos2];

            let args: Py<pyo3::types::PyTuple> = (symbol, ).into_py(py);
            let product = self.py_get_product.call1(py, args)?;
            let native_symbol: String = product.getattr(py, "native_symbol")?.extract(py)?;
            native_symbol
        };

        if !self.book_symbols.contains(&native_symbol) {
            return Ok(());
        }

        let py_book: &mut Py<BookBuilder> = self
            .books
            .entry(native_symbol.clone())
            .or_insert_with(|| Py::new(py, BookBuilder::new()).unwrap());
        {
            let mut book: PyRefMut<BookBuilder> = py_book.as_mut(py);
            book.clear();
            book.on_snapshot(
                record.timestamp,
                &record.data.tick.asks,
                &record.data.tick.bids,
            );
        }

        for callback in &self.book_callbacks {
            callback.call1(py, (native_symbol.clone(), py_book.to_object(py)))?;
        }
        Ok(())
    }
    fn on_trade(&mut self, _py: Python) {
        // trade_processor.
    }
    fn register_book_symbol(&mut self, symbol: &PyString) -> PyResult<()> {
        let symbol = symbol.to_string()?.into_owned();
        self.book_symbols.insert(symbol);
        Ok(())
    }
    fn register_book_callback(&mut self, callback: PyObject) {
        self.book_callbacks.push(callback);
    }
}
